package otel_zero

import (
	"context"
	"fmt"
	"io"
	"net/http"

	"gitee.com/Himan000/otel_zero/otel_zap_logger"
	"gitee.com/Himan000/otel_zero/otel_zap_logger/otel"
	"gitee.com/Himan000/otel_zero/otel_zap_logger/propagation/extract"
	"gitee.com/Himan000/otel_zero/zero_mdc_log"
	zero_config "gitee.com/Himan000/otel_zero/zero_mdc_log/config"
	"gitee.com/Himan000/otel_zero/zero_mdc_log/context_util"
	zero_log "gitee.com/Himan000/otel_zero/zero_mdc_log/log"
	"gitee.com/wxlao/eureka-client"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/rs/zerolog"
	"github.com/spf13/viper"
)

const (
	POST = "POST"
	GET  = "GET"
)

/*
入口otel、zero初始化，以及接收传入的gin参数（header/context）
*/
func Init(r *gin.Engine) {
	otel.Default.Init(r)                           // 初始时otel
	zero_log.Init()                                // 初始时zero
	r.Use(context_util.SetMdcFromHeaderMiddleware) // 将请求参数等存入MDC
}

/*
调用下一个请求时，将当前的MDC传入下一个请求。
*/
func NewReqeust(method, url string, body io.Reader) (*http.Response, error) {
	request, _ := http.NewRequest(method, url, body)
	ctx, _ := zero_log.MDC().Get("ctx")

	// 将当前的MDC传入下一个请求。
	otel.HttpInject(ctx.(context.Context), request)
	client := &http.Client{}

	traceparent := request.Header.Get("Traceparent")
	b3 := request.Header.Get("B3")

	if traceparent == "" && b3 != "" {
		traceparent = extract.ConvertTraceIdFromB3ToTraceparentFormat(b3)
		request.Header.Add("traceparent", traceparent)
	}

	context_util.SetLogContextToNewRequst(request)

	res, err := client.Do(request)
	return res, err
}

/*
调用链的请求包装。将traceid（context)相关注入otel
*/
func GetReqeustHeader() map[string]string {
	request, _ := http.NewRequest("", "", nil)
	// requestHeader := request.Header
	ctx, _ := zero_log.MDC().Get("ctx")

	// 如果是调用的入口，初始化traceid、把traceid所在的context加入MDC
	if ctx == nil {
		traceID := otel_zap_logger.GenTraceID()
		spanID := otel_zap_logger.GenSpanID()
		tmpCtx, _ := otel_zap_logger.NewRootContext(traceID, spanID)
		ctx = tmpCtx
		request.Header.Add("B3", traceID)
		zero_log.MDC().Set("ctx", ctx)
		zero_log.MDC().Set(zero_log.TRACE_ID, traceID)
		// zero_log.MDC().Set(zero_log.REQUEST_IP, getReqeustIpFromHeader(requestHeader))
	}

	// 将traceid的context和注入到otel
	otel.HttpInject(ctx.(context.Context), request)
	traceparent := request.Header.Get("Traceparent")
	b3 := request.Header.Get("B3")

	if traceparent == "" && b3 != "" {
		traceparent = extract.ConvertTraceIdFromB3ToTraceparentFormat(b3)
		request.Header.Add("traceparent", traceparent)
	}

	result := make(map[string]string)

	for k, v := range request.Header {
		value := ""

		for _, i := range v {
			value += i
		}

		result[k] = value
	}

	return result
}

func AppendOtelHeader(opts ...eureka.Option) []eureka.Option {
	headerOpts := eureka.WithHeaders(GetReqeustHeader())
	opts = append(opts, headerOpts)

	return opts
}

/*
 * 将日志相关需要协程安全的信息放到MDC: 通过header传入MDC。
 * 要使log能打印某个字段，需要两个条件：1. 将值设置在MDC；2. 初始化zero_log的时候将
 * 字段加入到zero_log（AddGlobalFields）。
 */
func LogContextMiddleware(c *gin.Context) {
	ctx := otel.Default.Start(c.Request.Context())
	zero_log.MDC().Set("ctx", ctx)                                     // 将ctx房到MDC，后面request请求可以用
	zero_log.MDC().Set(zero_log.TRACE_ID, otel.GetTraceId(ctx))        // 这个是调用链的trace_id
	zero_log.MDC().Set(zero_log.REQUEST_IP, getReqeustIpFromHeader(c)) // 存入MDC并传入下一个请求
	zero_log.MDC().Set(zero_log.REQUEST_ID, getRequestId(c))           // 存入MDC并传入下一个请求
	zero_log.MDC().Set(zero_log.USER_ID, getUserId(c))                 // 存入MDC并传入下一个请求
	zero_log.MDC().Set(zero_log.SESSION_ID, getSessionId(c))           // 存入MDC并传入下一个请求
	zero_log.MDC().Set(zero_log.APP_ID, getAppId())                    // 设置到MDC但不传入下一个请求
	zero_log.MDC().Set(zero_log.ENV_TYPE, getEnvType())                // 设置到MDC但不传入下一个请求

	c.Next()
	otel.End()
}

/*
 * 获取request ip: 先从header获取REQUEST_IP_KEY的值，如果没有，说明是从Nginx过来的请求。
 */
func getReqeustIpFromHeader(c *gin.Context) string {
	ip := c.Request.Header.Get(viper.GetString(zero_config.REQUEST_IP_KEY))

	if ip == "" {
		ip = c.ClientIP()
	}

	return ip
}

func getUserId(c *gin.Context) string {
	userIdKey := viper.GetString(zero_config.USER_ID_KEY)
	userID := c.GetHeader(userIdKey)

	if userID == "" {
		if u, ok := c.Get("userid"); ok {
			userID = fmt.Sprintf("%v", u)
		}

		if userID == "" {
			userID = "nouserid"
		}

		c.Header(userIdKey, userID)
	}

	return userID
}

func getRequestId(c *gin.Context) string {
	requestIdKey := viper.GetString(zero_config.REQUEST_ID_KEY)
	requestID := c.GetHeader(requestIdKey)

	if requestID == "" {
		requestID = uuid.New().String()
		c.Header(requestIdKey, requestID)
	}

	return requestID
}

func getSessionId(c *gin.Context) string {
	sessionIdKey := viper.GetString(zero_config.SESSION_ID_KEY)
	sessionID := c.GetHeader(sessionIdKey)

	if sessionID == "" {
		sessionID = "nosessionid"
		c.Header(sessionIdKey, sessionID)
	}

	return sessionID
}

func getAppId() string {
	return viper.GetString(zero_config.APP_ID)
}

func getEnvType() string {
	return viper.GetString(zero_config.ENV_TYPE)
}

func MDC() *zero_mdc_log.MdcAdapter {
	return zero_log.MDC()
}

func Log() *zero_mdc_log.Overlog {
	return zero_log.Log()
}

func Info() *zerolog.Event {
	return zero_log.Log().Info()
}

func Debug() *zerolog.Event {
	return zero_log.Log().Debug()
}

func Error() *zerolog.Event {
	return zero_log.Log().Error()
}

func Panic() *zerolog.Event {
	return zero_log.Log().Panic()
}

// 设置请求的middleware，每个请求写一些日志
func SetLogger(configItems ...zero_mdc_log.ConfigItem) gin.HandlerFunc {
	return zero_mdc_log.SetLogger(configItems...)
}
